package authservice

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"games.com/server/internal/pkg/config"
	pkghttp "games.com/server/internal/pkg/http"
	"golang.org/x/crypto/bcrypt"
	"io"
	"net/http"
)

type authService struct {
	dao    *authDAO
	config *config.Config
}

func NewAuthService(config *config.Config) (*authService, error) {
	as := &authService{config: config}

	dao, err := NewAuthDAO(config)
	if err != nil {
		return nil, fmt.Errorf("failed to new authdao, err:%w", err)
	}
	as.dao = dao

	http.HandleFunc("/Echo", as.echoHandler)
	http.HandleFunc("/Register", as.registerHandler)
	http.HandleFunc("/Login", as.loginHandler)
	http.HandleFunc("/QueryGMList", as.queryGMListHandler)

	return as, nil
}

func (as *authService) echoHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodOptions {
		allowCrossRegion(w)
		w.WriteHeader(http.StatusOK)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		pkghttp.HttpError(w, fmt.Errorf("error read request body, err:%w", err).Error(), http.StatusInternalServerError)
		return
	}

	_, err = w.Write(body)
	if err != nil {
		pkghttp.HttpError(w, fmt.Errorf("error write response body, err:%w", err).Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
}

func (as *authService) registerHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodOptions {
		allowCrossRegion(w)
		w.WriteHeader(http.StatusOK)
		return
	}

	if r.Method != http.MethodPost {
		pkghttp.HttpError(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	var user User
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		pkghttp.HttpError(w, err.Error(), http.StatusBadRequest)
		return
	}

	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
	if err != nil {
		pkghttp.HttpError(w, fmt.Errorf("error while hashing password, caused by %w", err).Error(), http.StatusInternalServerError)
		return
	}
	user.Password = string(hashedPassword)

	ctx := r.Context()
	err = as.dao.InsertUser(ctx, &user)
	if err != nil {
		pkghttp.HttpError(w, fmt.Errorf("error while insert user to db, caused by %w", err).Error(), http.StatusInternalServerError)
		return
	}

	allowCrossRegion(w)
	w.WriteHeader(http.StatusOK)
}

func (as *authService) loginHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodOptions {
		allowCrossRegion(w)
		w.WriteHeader(http.StatusOK)
		return
	}

	if r.Method != http.MethodPost {
		pkghttp.HttpError(w, "only post method is allowed", http.StatusMethodNotAllowed)
		return
	}

	var user User
	err := json.NewDecoder(r.Body).Decode(&user)
	if err != nil {
		pkghttp.HttpError(w, err.Error(), http.StatusBadRequest)
		return
	}

	ctx := r.Context()
	dbUser, err := as.dao.QueryUser(ctx, user.Username)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			pkghttp.HttpError(w, err.Error(), http.StatusUnauthorized)
			return
		}
		pkghttp.HttpError(w, err.Error(), http.StatusInternalServerError)
		return
	}

	err = bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(user.Password))
	if err != nil {
		pkghttp.HttpError(w, fmt.Errorf("invalid login credentials, err:%w", err).Error(), http.StatusUnauthorized)
		return
	}

	allowCrossRegion(w)
	w.WriteHeader(http.StatusOK)
}

type GMUsernames struct {
	GMs []string `json:"gms"`
}

func (as *authService) queryGMListHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodOptions {
		allowCrossRegion(w)
		w.WriteHeader(http.StatusOK)
		return
	}

	if r.Method != http.MethodPost {
		pkghttp.HttpError(w, "only post method is allowed", http.StatusMethodNotAllowed)
		return
	}

	ctx := r.Context()
	dbUsers, err := as.dao.QueryUserList(ctx)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			pkghttp.HttpError(w, err.Error(), http.StatusUnauthorized)
			return
		}
		pkghttp.HttpError(w, err.Error(), http.StatusInternalServerError)
		return
	}

	gmUsers := &GMUsernames{GMs: make([]string, 0, len(dbUsers))}
	for _, u := range dbUsers {
		gmUsers.GMs = append(gmUsers.GMs, u.Username)
	}
	resBytes, err := json.Marshal(gmUsers)
	if err != nil {
		pkghttp.HttpError(w, err.Error(), http.StatusInternalServerError)
		return
	}

	allowCrossRegion(w)
	w.WriteHeader(http.StatusOK)
	w.Write(resBytes)
}

func allowCrossRegion(w http.ResponseWriter) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
}
